// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_storage.h"

#include <stdint.h>
#include <string>
#include <utility>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_disk_cache.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/service_worker_status_code.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_trial_policy.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_message.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::IOBuffer;
using net::TestCompletionCallback;
using net::WrappedIOBuffer;

namespace content {

namespace {

    typedef ServiceWorkerDatabase::RegistrationData RegistrationData;
    typedef ServiceWorkerDatabase::ResourceRecord ResourceRecord;

    // This is a sample public key for testing the API. The corresponding private
    // key (use this to generate new samples for this test file) is:
    //
    //  0x83, 0x67, 0xf4, 0xcd, 0x2a, 0x1f, 0x0e, 0x04, 0x0d, 0x43, 0x13,
    //  0x4c, 0x67, 0xc4, 0xf4, 0x28, 0xc9, 0x90, 0x15, 0x02, 0xe2, 0xba,
    //  0xfd, 0xbb, 0xfa, 0xbc, 0x92, 0x76, 0x8a, 0x2c, 0x4b, 0xc7, 0x75,
    //  0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2, 0x9a,
    //  0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f, 0x64,
    //  0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0
    const uint8_t kTestPublicKey[] = {
        0x75,
        0x10,
        0xac,
        0xf9,
        0x3a,
        0x1c,
        0xb8,
        0xa9,
        0x28,
        0x70,
        0xd2,
        0x9a,
        0xd0,
        0x0b,
        0x59,
        0xe1,
        0xac,
        0x2b,
        0xb7,
        0xd5,
        0xca,
        0x1f,
        0x64,
        0x90,
        0x08,
        0x8e,
        0xa8,
        0xe0,
        0x56,
        0x3a,
        0x04,
        0xd0,
    };

    void StatusAndQuitCallback(ServiceWorkerStatusCode* result,
        const base::Closure& quit_closure,
        ServiceWorkerStatusCode status)
    {
        *result = status;
        quit_closure.Run();
    }

    void StatusCallback(bool* was_called,
        ServiceWorkerStatusCode* result,
        ServiceWorkerStatusCode status)
    {
        *was_called = true;
        *result = status;
    }

    ServiceWorkerStorage::StatusCallback MakeStatusCallback(
        bool* was_called,
        ServiceWorkerStatusCode* result)
    {
        return base::Bind(&StatusCallback, was_called, result);
    }

    void FindCallback(bool* was_called,
        ServiceWorkerStatusCode* result,
        scoped_refptr<ServiceWorkerRegistration>* found,
        ServiceWorkerStatusCode status,
        scoped_refptr<ServiceWorkerRegistration> registration)
    {
        *was_called = true;
        *result = status;
        *found = std::move(registration);
    }

    ServiceWorkerStorage::FindRegistrationCallback MakeFindCallback(
        bool* was_called,
        ServiceWorkerStatusCode* result,
        scoped_refptr<ServiceWorkerRegistration>* found)
    {
        return base::Bind(&FindCallback, was_called, result, found);
    }

    void GetAllCallback(
        bool* was_called,
        ServiceWorkerStatusCode* result,
        std::vector<scoped_refptr<ServiceWorkerRegistration>>* all_out,
        ServiceWorkerStatusCode status,
        const std::vector<scoped_refptr<ServiceWorkerRegistration>>& all)
    {
        *was_called = true;
        *result = status;
        *all_out = all;
    }

    void GetAllInfosCallback(
        bool* was_called,
        ServiceWorkerStatusCode* result,
        std::vector<ServiceWorkerRegistrationInfo>* all_out,
        ServiceWorkerStatusCode status,
        const std::vector<ServiceWorkerRegistrationInfo>& all)
    {
        *was_called = true;
        *result = status;
        *all_out = all;
    }

    ServiceWorkerStorage::GetRegistrationsCallback MakeGetRegistrationsCallback(
        bool* was_called,
        ServiceWorkerStatusCode* status,
        std::vector<scoped_refptr<ServiceWorkerRegistration>>* all)
    {
        return base::Bind(&GetAllCallback, was_called, status, all);
    }

    ServiceWorkerStorage::GetRegistrationsInfosCallback
    MakeGetRegistrationsInfosCallback(
        bool* was_called,
        ServiceWorkerStatusCode* status,
        std::vector<ServiceWorkerRegistrationInfo>* all)
    {
        return base::Bind(&GetAllInfosCallback, was_called, status, all);
    }

    void GetUserDataCallback(bool* was_called,
        std::vector<std::string>* data_out,
        ServiceWorkerStatusCode* status_out,
        const std::vector<std::string>& data,
        ServiceWorkerStatusCode status)
    {
        *was_called = true;
        *data_out = data;
        *status_out = status;
    }

    void GetUserDataForAllRegistrationsCallback(
        bool* was_called,
        std::vector<std::pair<int64_t, std::string>>* data_out,
        ServiceWorkerStatusCode* status_out,
        const std::vector<std::pair<int64_t, std::string>>& data,
        ServiceWorkerStatusCode status)
    {
        *was_called = true;
        *data_out = data;
        *status_out = status;
    }

    int WriteResponse(ServiceWorkerStorage* storage,
        int64_t id,
        const std::string& headers,
        IOBuffer* body,
        int length)
    {
        std::unique_ptr<ServiceWorkerResponseWriter> writer = storage->CreateResponseWriter(id);

        std::unique_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo);
        info->request_time = base::Time::Now();
        info->response_time = base::Time::Now();
        info->was_cached = false;
        info->headers = new net::HttpResponseHeaders(headers);
        scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = new HttpResponseInfoIOBuffer(info.release());
        int rv = 0;
        {
            TestCompletionCallback cb;
            writer->WriteInfo(info_buffer.get(), cb.callback());
            rv = cb.WaitForResult();
            if (rv < 0)
                return rv;
        }
        {
            TestCompletionCallback cb;
            writer->WriteData(body, length, cb.callback());
            rv = cb.WaitForResult();
        }
        return rv;
    }

    int WriteStringResponse(ServiceWorkerStorage* storage,
        int64_t id,
        const std::string& headers,
        const std::string& body)
    {
        scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(body.data()));
        return WriteResponse(storage, id, headers, body_buffer.get(), body.length());
    }

    int WriteBasicResponse(ServiceWorkerStorage* storage, int64_t id)
    {
        const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\0Content-Length: 5\0\0";
        const char kHttpBody[] = "Hello";
        std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
        return WriteStringResponse(storage, id, headers, std::string(kHttpBody));
    }

    int ReadResponseInfo(ServiceWorkerStorage* storage,
        int64_t id,
        HttpResponseInfoIOBuffer* info_buffer)
    {
        std::unique_ptr<ServiceWorkerResponseReader> reader = storage->CreateResponseReader(id);
        TestCompletionCallback cb;
        reader->ReadInfo(info_buffer, cb.callback());
        return cb.WaitForResult();
    }

    bool VerifyBasicResponse(ServiceWorkerStorage* storage,
        int64_t id,
        bool expected_positive_result)
    {
        const std::string kExpectedHttpBody("Hello");
        std::unique_ptr<ServiceWorkerResponseReader> reader = storage->CreateResponseReader(id);
        scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = new HttpResponseInfoIOBuffer();
        int rv = ReadResponseInfo(storage, id, info_buffer.get());
        if (expected_positive_result)
            EXPECT_LT(0, rv);
        if (rv <= 0)
            return false;

        std::string received_body;
        const int kBigEnough = 512;
        scoped_refptr<net::IOBuffer> buffer = new IOBuffer(kBigEnough);
        TestCompletionCallback cb;
        reader->ReadData(buffer.get(), kBigEnough, cb.callback());
        rv = cb.WaitForResult();
        EXPECT_EQ(static_cast<int>(kExpectedHttpBody.size()), rv);
        if (rv <= 0)
            return false;
        received_body.assign(buffer->data(), rv);

        bool status_match = std::string("HONKYDORY") == info_buffer->http_info->headers->GetStatusText();
        bool data_match = kExpectedHttpBody == received_body;

        EXPECT_TRUE(status_match);
        EXPECT_TRUE(data_match);
        return status_match && data_match;
    }

    int WriteResponseMetadata(ServiceWorkerStorage* storage,
        int64_t id,
        const std::string& metadata)
    {
        scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(metadata.data()));
        std::unique_ptr<ServiceWorkerResponseMetadataWriter> metadata_writer = storage->CreateResponseMetadataWriter(id);
        TestCompletionCallback cb;
        metadata_writer->WriteMetadata(body_buffer.get(), metadata.length(),
            cb.callback());
        return cb.WaitForResult();
    }

    int WriteMetadata(ServiceWorkerVersion* version,
        const GURL& url,
        const std::string& metadata)
    {
        const std::vector<char> data(metadata.begin(), metadata.end());
        EXPECT_TRUE(version);
        TestCompletionCallback cb;
        version->script_cache_map()->WriteMetadata(url, data, cb.callback());
        return cb.WaitForResult();
    }

    int ClearMetadata(ServiceWorkerVersion* version, const GURL& url)
    {
        EXPECT_TRUE(version);
        TestCompletionCallback cb;
        version->script_cache_map()->ClearMetadata(url, cb.callback());
        return cb.WaitForResult();
    }

    bool VerifyResponseMetadata(ServiceWorkerStorage* storage,
        int64_t id,
        const std::string& expected_metadata)
    {
        std::unique_ptr<ServiceWorkerResponseReader> reader = storage->CreateResponseReader(id);
        scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = new HttpResponseInfoIOBuffer();
        {
            TestCompletionCallback cb;
            reader->ReadInfo(info_buffer.get(), cb.callback());
            int rv = cb.WaitForResult();
            EXPECT_LT(0, rv);
        }
        const net::HttpResponseInfo* read_head = info_buffer->http_info.get();
        if (!read_head->metadata.get())
            return false;
        EXPECT_EQ(0, memcmp(expected_metadata.data(), read_head->metadata->data(), expected_metadata.length()));
        return true;
    }

} // namespace

class ServiceWorkerStorageTest : public testing::Test {
public:
    ServiceWorkerStorageTest()
        : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP)
    {
    }

    void SetUp() override { InitializeTestHelper(); }

    void TearDown() override
    {
        helper_.reset();
        base::RunLoop().RunUntilIdle();
    }

    bool InitUserDataDirectory()
    {
        if (!user_data_directory_.CreateUniqueTempDir())
            return false;
        user_data_directory_path_ = user_data_directory_.GetPath();
        return true;
    }

    void InitializeTestHelper()
    {
        helper_.reset(new EmbeddedWorkerTestHelper(user_data_directory_path_));
        base::RunLoop().RunUntilIdle();
    }

    ServiceWorkerContextCore* context() { return helper_->context(); }
    ServiceWorkerStorage* storage() { return helper_->context()->storage(); }

    // A static class method for friendliness.
    static void VerifyPurgeableListStatusCallback(
        ServiceWorkerDatabase* database,
        std::set<int64_t>* purgeable_ids,
        bool* was_called,
        ServiceWorkerStatusCode* result,
        ServiceWorkerStatusCode status)
    {
        *was_called = true;
        *result = status;
        EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
            database->GetPurgeableResourceIds(purgeable_ids));
    }

protected:
    void LazyInitialize()
    {
        storage()->LazyInitialize(base::Bind(&base::DoNothing));
        base::RunLoop().RunUntilIdle();
    }

    ServiceWorkerStatusCode StoreRegistration(
        scoped_refptr<ServiceWorkerRegistration> registration,
        scoped_refptr<ServiceWorkerVersion> version)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->StoreRegistration(registration.get(),
            version.get(),
            MakeStatusCallback(&was_called, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode DeleteRegistration(int64_t registration_id,
        const GURL& origin)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->DeleteRegistration(
            registration_id, origin, MakeStatusCallback(&was_called, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode GetAllRegistrationsInfos(
        std::vector<ServiceWorkerRegistrationInfo>* registrations)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->GetAllRegistrationsInfos(
            MakeGetRegistrationsInfosCallback(&was_called, &result, registrations));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode GetRegistrationsForOrigin(
        const GURL& origin,
        std::vector<scoped_refptr<ServiceWorkerRegistration>>* registrations)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->GetRegistrationsForOrigin(
            origin,
            MakeGetRegistrationsCallback(&was_called, &result, registrations));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode GetUserData(int64_t registration_id,
        const std::vector<std::string>& keys,
        std::vector<std::string>* data)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->GetUserData(
            registration_id, keys,
            base::Bind(&GetUserDataCallback, &was_called, data, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode StoreUserData(
        int64_t registration_id,
        const GURL& origin,
        const std::vector<std::pair<std::string, std::string>>& key_value_pairs)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->StoreUserData(registration_id, origin, key_value_pairs,
            MakeStatusCallback(&was_called, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode ClearUserData(int64_t registration_id,
        const std::vector<std::string>& keys)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->ClearUserData(registration_id, keys,
            MakeStatusCallback(&was_called, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode GetUserDataForAllRegistrations(
        const std::string& key,
        std::vector<std::pair<int64_t, std::string>>* data)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->GetUserDataForAllRegistrations(
            key, base::Bind(&GetUserDataForAllRegistrationsCallback, &was_called, data, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode UpdateToActiveState(
        scoped_refptr<ServiceWorkerRegistration> registration)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->UpdateToActiveState(registration.get(),
            MakeStatusCallback(&was_called, &result));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    void UpdateLastUpdateCheckTime(
        scoped_refptr<ServiceWorkerRegistration> registration)
    {
        storage()->UpdateLastUpdateCheckTime(registration.get());
        base::RunLoop().RunUntilIdle();
    }

    ServiceWorkerStatusCode FindRegistrationForDocument(
        const GURL& document_url,
        scoped_refptr<ServiceWorkerRegistration>* registration)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->FindRegistrationForDocument(
            document_url, MakeFindCallback(&was_called, &result, registration));
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode FindRegistrationForPattern(
        const GURL& scope,
        scoped_refptr<ServiceWorkerRegistration>* registration)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->FindRegistrationForPattern(
            scope, MakeFindCallback(&was_called, &result, registration));
        EXPECT_FALSE(was_called); // always async
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode FindRegistrationForId(
        int64_t registration_id,
        const GURL& origin,
        scoped_refptr<ServiceWorkerRegistration>* registration)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->FindRegistrationForId(
            registration_id, origin,
            MakeFindCallback(&was_called, &result, registration));
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    ServiceWorkerStatusCode FindRegistrationForIdOnly(
        int64_t registration_id,
        scoped_refptr<ServiceWorkerRegistration>* registration)
    {
        bool was_called = false;
        ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
        storage()->FindRegistrationForIdOnly(
            registration_id, MakeFindCallback(&was_called, &result, registration));
        base::RunLoop().RunUntilIdle();
        EXPECT_TRUE(was_called);
        return result;
    }

    // Directly writes a registration using
    // ServiceWorkerDatabase::WriteRegistration rather than
    // ServiceWorkerStorage::StoreRegistration. Useful for simulating a
    // registration written by an earlier version of Chrome.
    void WriteRegistration(const RegistrationData& registration,
        const std::vector<ResourceRecord>& resources)
    {
        ServiceWorkerDatabase::RegistrationData deleted_version;
        std::vector<int64_t> newly_purgeable_resources;

        ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
            storage()->database_->WriteRegistration(
                registration, resources, &deleted_version,
                &newly_purgeable_resources));
    }

    // user_data_directory_ must be declared first to preserve destructor order.
    base::ScopedTempDir user_data_directory_;
    base::FilePath user_data_directory_path_;
    std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
    TestBrowserThreadBundle browser_thread_bundle_;
};

TEST_F(ServiceWorkerStorageTest, DisabledStorage)
{
    const GURL kScope("http://www.example.com/scope/");
    const GURL kScript("http://www.example.com/script.js");
    const GURL kDocumentUrl("http://www.example.com/scope/document.html");
    const int64_t kRegistrationId = 0;
    const int64_t kVersionId = 0;
    const int64_t kResourceId = 0;

    LazyInitialize();
    storage()->Disable();

    scoped_refptr<ServiceWorkerRegistration> found_registration;
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        FindRegistrationForId(kRegistrationId, kScope.GetOrigin(),
            &found_registration));
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    EXPECT_FALSE(storage()->GetUninstallingRegistration(kScope.GetOrigin()));

    std::vector<scoped_refptr<ServiceWorkerRegistration>> found_registrations;
    EXPECT_EQ(
        SERVICE_WORKER_ERROR_ABORT,
        GetRegistrationsForOrigin(kScope.GetOrigin(), &found_registrations));

    std::vector<ServiceWorkerRegistrationInfo> all_registrations;
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        GetAllRegistrationsInfos(&all_registrations));

    scoped_refptr<ServiceWorkerRegistration> live_registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
        live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        StoreRegistration(live_registration, live_version));

    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT, UpdateToActiveState(live_registration));

    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        DeleteRegistration(kRegistrationId, kScope.GetOrigin()));

    // Response reader and writer created by the disabled storage should fail to
    // access the disk cache.
    scoped_refptr<HttpResponseInfoIOBuffer> info_buffer = new HttpResponseInfoIOBuffer();
    EXPECT_EQ(net::ERR_CACHE_MISS,
        ReadResponseInfo(storage(), kResourceId, info_buffer.get()));
    EXPECT_EQ(net::ERR_FAILED, WriteBasicResponse(storage(), kResourceId));
    EXPECT_EQ(net::ERR_FAILED,
        WriteResponseMetadata(storage(), kResourceId, "foo"));

    const std::string kUserDataKey = "key";
    std::vector<std::string> user_data_out;
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        GetUserData(kRegistrationId, { kUserDataKey }, &user_data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        StoreUserData(kRegistrationId, kScope.GetOrigin(),
            { { kUserDataKey, "foo" } }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        ClearUserData(kRegistrationId, { kUserDataKey }));
    std::vector<std::pair<int64_t, std::string>> data_list_out;
    EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
        GetUserDataForAllRegistrations(kUserDataKey, &data_list_out));

    EXPECT_FALSE(
        storage()->OriginHasForeignFetchRegistrations(kScope.GetOrigin()));

    // Next available ids should be invalid.
    EXPECT_EQ(kInvalidServiceWorkerRegistrationId,
        storage()->NewRegistrationId());
    EXPECT_EQ(kInvalidServiceWorkerVersionId, storage()->NewVersionId());
    EXPECT_EQ(kInvalidServiceWorkerResourceId, storage()->NewRegistrationId());
}

TEST_F(ServiceWorkerStorageTest, StoreFindUpdateDeleteRegistration)
{
    const GURL kScope("http://www.test.not/scope/");
    const GURL kDocumentUrl("http://www.test.not/scope/document.html");
    const GURL kResource1("http://www.test.not/scope/resource1.js");
    const int64_t kResource1Size = 1591234;
    const GURL kResource2("http://www.test.not/scope/resource2.js");
    const int64_t kResource2Size = 51;
    const int64_t kRegistrationId = 0;
    const int64_t kVersionId = 0;
    const GURL kForeignFetchScope("http://www.test.not/scope/ff/");
    const url::Origin kForeignFetchOrigin(GURL("https://example.com/"));
    const base::Time kToday = base::Time::Now();
    const base::Time kYesterday = kToday - base::TimeDelta::FromDays(1);

    scoped_refptr<ServiceWorkerRegistration> found_registration;

    // We shouldn't find anything without having stored anything.
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    EXPECT_FALSE(found_registration.get());

    std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
    resources.push_back(
        ServiceWorkerDatabase::ResourceRecord(1, kResource1, kResource1Size));
    resources.push_back(
        ServiceWorkerDatabase::ResourceRecord(2, kResource2, kResource2Size));

    // Store something.
    scoped_refptr<ServiceWorkerRegistration> live_registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
        live_registration.get(), kResource1, kVersionId, context()->AsWeakPtr());
    live_version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_version->script_cache_map()->SetResources(resources);
    live_version->set_foreign_fetch_scopes(
        std::vector<GURL>(1, kForeignFetchScope));
    live_version->set_foreign_fetch_origins(
        std::vector<url::Origin>(1, kForeignFetchOrigin));
    live_registration->SetWaitingVersion(live_version);
    live_registration->set_last_update_check(kYesterday);
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration, live_version));

    // Now we should find it and get the live ptr back immediately.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    EXPECT_EQ(kResource1Size + kResource2Size,
        live_registration->resources_total_size_bytes());
    EXPECT_EQ(kResource1Size + kResource2Size,
        found_registration->resources_total_size_bytes());
    found_registration = NULL;

    // But FindRegistrationForPattern is always async.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    // Can be found by id too.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    ASSERT_TRUE(found_registration.get());
    EXPECT_EQ(kRegistrationId, found_registration->id());
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    // Can be found by just the id too.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    ASSERT_TRUE(found_registration.get());
    EXPECT_EQ(kRegistrationId, found_registration->id());
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    // Drop the live registration, but keep the version live.
    live_registration = NULL;

    // Now FindRegistrationForDocument should be async.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    ASSERT_TRUE(found_registration.get());
    EXPECT_EQ(kRegistrationId, found_registration->id());
    EXPECT_TRUE(found_registration->HasOneRef());

    // Check that sizes are populated correctly
    EXPECT_EQ(live_version.get(), found_registration->waiting_version());
    EXPECT_EQ(kResource1Size + kResource2Size,
        found_registration->resources_total_size_bytes());
    std::vector<ServiceWorkerRegistrationInfo> all_registrations;
    EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
    EXPECT_EQ(1u, all_registrations.size());
    ServiceWorkerRegistrationInfo info = all_registrations[0];
    EXPECT_EQ(kResource1Size + kResource2Size, info.stored_version_size_bytes);
    all_registrations.clear();

    // Finding by origin should provide the same result if origin is kScope.
    std::vector<scoped_refptr<ServiceWorkerRegistration>>
        registrations_for_origin;
    EXPECT_EQ(
        SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
    EXPECT_EQ(1u, registrations_for_origin.size());
    registrations_for_origin.clear();

    EXPECT_EQ(SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
            &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());

    found_registration = NULL;

    // Drop the live version too.
    live_version = NULL;

    // And FindRegistrationForPattern is always async.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForPattern(kScope, &found_registration));
    ASSERT_TRUE(found_registration.get());
    EXPECT_EQ(kRegistrationId, found_registration->id());
    EXPECT_TRUE(found_registration->HasOneRef());
    EXPECT_FALSE(found_registration->active_version());
    ASSERT_TRUE(found_registration->waiting_version());
    EXPECT_EQ(kYesterday, found_registration->last_update_check());
    EXPECT_EQ(ServiceWorkerVersion::INSTALLED,
        found_registration->waiting_version()->status());
    EXPECT_EQ(
        1u, found_registration->waiting_version()->foreign_fetch_scopes().size());
    EXPECT_EQ(kForeignFetchScope,
        found_registration->waiting_version()->foreign_fetch_scopes()[0]);
    EXPECT_EQ(
        1u,
        found_registration->waiting_version()->foreign_fetch_origins().size());
    EXPECT_EQ(kForeignFetchOrigin,
        found_registration->waiting_version()->foreign_fetch_origins()[0]);

    // Update to active and update the last check time.
    scoped_refptr<ServiceWorkerVersion> temp_version = found_registration->waiting_version();
    temp_version->SetStatus(ServiceWorkerVersion::ACTIVATED);
    found_registration->SetActiveVersion(temp_version);
    temp_version = NULL;
    EXPECT_EQ(SERVICE_WORKER_OK, UpdateToActiveState(found_registration));
    found_registration->set_last_update_check(kToday);
    UpdateLastUpdateCheckTime(found_registration.get());

    found_registration = NULL;

    // Trying to update a unstored registration to active should fail.
    scoped_refptr<ServiceWorkerRegistration> unstored_registration = new ServiceWorkerRegistration(kScope, kRegistrationId + 1,
        context()->AsWeakPtr());
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        UpdateToActiveState(unstored_registration));
    unstored_registration = NULL;

    // The Find methods should return a registration with an active version
    // and the expected update time.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    ASSERT_TRUE(found_registration.get());
    EXPECT_EQ(kRegistrationId, found_registration->id());
    EXPECT_TRUE(found_registration->HasOneRef());
    EXPECT_FALSE(found_registration->waiting_version());
    ASSERT_TRUE(found_registration->active_version());
    EXPECT_EQ(ServiceWorkerVersion::ACTIVATED,
        found_registration->active_version()->status());
    EXPECT_EQ(kToday, found_registration->last_update_check());

    // Delete from storage but with a instance still live.
    EXPECT_TRUE(context()->GetLiveVersion(kRegistrationId));
    EXPECT_EQ(SERVICE_WORKER_OK,
        DeleteRegistration(kRegistrationId, kScope.GetOrigin()));
    EXPECT_TRUE(context()->GetLiveVersion(kRegistrationId));

    // Should no longer be found.
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    EXPECT_FALSE(found_registration.get());
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    EXPECT_FALSE(found_registration.get());

    // Deleting an unstored registration should succeed.
    EXPECT_EQ(SERVICE_WORKER_OK,
        DeleteRegistration(kRegistrationId + 1, kScope.GetOrigin()));
}

TEST_F(ServiceWorkerStorageTest, InstallingRegistrationsAreFindable)
{
    const GURL kScope("http://www.test.not/scope/");
    const GURL kScript("http://www.test.not/script.js");
    const GURL kDocumentUrl("http://www.test.not/scope/document.html");
    const int64_t kRegistrationId = 0;
    const int64_t kVersionId = 0;

    scoped_refptr<ServiceWorkerRegistration> found_registration;

    // Create an unstored registration.
    scoped_refptr<ServiceWorkerRegistration> live_registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
        live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
    live_version->SetStatus(ServiceWorkerVersion::INSTALLING);
    live_registration->SetWaitingVersion(live_version);

    // Should not be findable, including by GetAllRegistrationsInfos.
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_FALSE(found_registration.get());

    std::vector<ServiceWorkerRegistrationInfo> all_registrations;
    EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
    EXPECT_TRUE(all_registrations.empty());

    std::vector<scoped_refptr<ServiceWorkerRegistration>>
        registrations_for_origin;
    EXPECT_EQ(
        SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());

    EXPECT_EQ(SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
            &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());

    // Notify storage of it being installed.
    storage()->NotifyInstallingRegistration(live_registration.get());

    // Now should be findable.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_EQ(live_registration, found_registration);
    found_registration = NULL;

    EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
    EXPECT_EQ(1u, all_registrations.size());
    all_registrations.clear();

    // Finding by origin should provide the same result if origin is kScope.
    EXPECT_EQ(
        SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
    EXPECT_EQ(1u, registrations_for_origin.size());
    registrations_for_origin.clear();

    EXPECT_EQ(SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
            &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());

    // Notify storage of installation no longer happening.
    storage()->NotifyDoneInstallingRegistration(
        live_registration.get(), NULL, SERVICE_WORKER_OK);

    // Once again, should not be findable.
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForId(
            kRegistrationId, kScope.GetOrigin(), &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForIdOnly(kRegistrationId, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        FindRegistrationForPattern(kScope, &found_registration));
    EXPECT_FALSE(found_registration.get());

    EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
    EXPECT_TRUE(all_registrations.empty());

    EXPECT_EQ(
        SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());

    EXPECT_EQ(SERVICE_WORKER_OK,
        GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
            &registrations_for_origin));
    EXPECT_TRUE(registrations_for_origin.empty());
}

TEST_F(ServiceWorkerStorageTest, StoreUserData)
{
    const GURL kScope("http://www.test.not/scope/");
    const GURL kScript("http://www.test.not/script.js");
    const int64_t kRegistrationId = 0;
    const int64_t kVersionId = 0;

    LazyInitialize();

    // Store a registration.
    scoped_refptr<ServiceWorkerRegistration> live_registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
        live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records;
    records.push_back(ServiceWorkerDatabase::ResourceRecord(
        1, live_version->script_url(), 100));
    live_version->script_cache_map()->SetResources(records);
    live_version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_registration->SetWaitingVersion(live_version);
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration, live_version));

    // Store user data associated with the registration.
    std::vector<std::string> data_out;
    EXPECT_EQ(
        SERVICE_WORKER_OK,
        StoreUserData(kRegistrationId, kScope.GetOrigin(), { { "key", "data" } }));
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserData(kRegistrationId, { "key" }, &data_out));
    ASSERT_EQ(1u, data_out.size());
    EXPECT_EQ("data", data_out[0]);
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "unknown_key" }, &data_out));
    std::vector<std::pair<int64_t, std::string>> data_list_out;
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserDataForAllRegistrations("key", &data_list_out));
    ASSERT_EQ(1u, data_list_out.size());
    EXPECT_EQ(kRegistrationId, data_list_out[0].first);
    EXPECT_EQ("data", data_list_out[0].second);
    data_list_out.clear();
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserDataForAllRegistrations("unknown_key", &data_list_out));
    EXPECT_EQ(0u, data_list_out.size());
    EXPECT_EQ(SERVICE_WORKER_OK, ClearUserData(kRegistrationId, { "key" }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key" }, &data_out));

    // Write/overwrite multiple user data keys.
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreUserData(
            kRegistrationId, kScope.GetOrigin(),
            { { "key", "overwrite" }, { "key3", "data3" }, { "key4", "data4" } }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key2" }, &data_out));
    EXPECT_TRUE(data_out.empty());
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserData(kRegistrationId, { "key", "key3", "key4" }, &data_out));
    ASSERT_EQ(3u, data_out.size());
    EXPECT_EQ("overwrite", data_out[0]);
    EXPECT_EQ("data3", data_out[1]);
    EXPECT_EQ("data4", data_out[2]);
    // Multiple gets fail if one is not found.
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key", "key2" }, &data_out));
    EXPECT_TRUE(data_out.empty());

    // Delete multiple user data keys, even if some are not found.
    EXPECT_EQ(SERVICE_WORKER_OK,
        ClearUserData(kRegistrationId, { "key", "key2", "key3" }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key" }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key2" }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key3" }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserData(kRegistrationId, { "key4" }, &data_out));
    ASSERT_EQ(1u, data_out.size());
    EXPECT_EQ("data4", data_out[0]);

    // User data should be deleted when the associated registration is deleted.
    ASSERT_EQ(
        SERVICE_WORKER_OK,
        StoreUserData(kRegistrationId, kScope.GetOrigin(), { { "key", "data" } }));
    ASSERT_EQ(SERVICE_WORKER_OK,
        GetUserData(kRegistrationId, { "key" }, &data_out));
    ASSERT_EQ(1u, data_out.size());
    ASSERT_EQ("data", data_out[0]);

    EXPECT_EQ(SERVICE_WORKER_OK,
        DeleteRegistration(kRegistrationId, kScope.GetOrigin()));
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key" }, &data_out));
    data_list_out.clear();
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserDataForAllRegistrations("key", &data_list_out));
    EXPECT_EQ(0u, data_list_out.size());

    // Data access with an invalid registration id should be failed.
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        StoreUserData(kInvalidServiceWorkerRegistrationId,
            kScope.GetOrigin(), { { "key", "data" } }));
    EXPECT_EQ(
        SERVICE_WORKER_ERROR_FAILED,
        GetUserData(kInvalidServiceWorkerRegistrationId, { "key" }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        ClearUserData(kInvalidServiceWorkerRegistrationId, { "key" }));

    // Data access with an empty key should be failed.
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        StoreUserData(kRegistrationId, kScope.GetOrigin(),
            std::vector<std::pair<std::string, std::string>>()));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        StoreUserData(kRegistrationId, kScope.GetOrigin(),
            { { std::string(), "data" } }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        StoreUserData(kRegistrationId, kScope.GetOrigin(),
            { { std::string(), "data" }, { "key", "data" } }));
    EXPECT_EQ(
        SERVICE_WORKER_ERROR_FAILED,
        GetUserData(kRegistrationId, std::vector<std::string>(), &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        GetUserData(kRegistrationId, { std::string() }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        GetUserData(kRegistrationId, { std::string(), "key" }, &data_out));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        ClearUserData(kRegistrationId, std::vector<std::string>()));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        ClearUserData(kRegistrationId, { std::string() }));
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        ClearUserData(kRegistrationId, { std::string(), "key" }));
    data_list_out.clear();
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
        GetUserDataForAllRegistrations(std::string(), &data_list_out));
}

// The *_BeforeInitialize tests exercise the API before LazyInitialize() is
// called.
TEST_F(ServiceWorkerStorageTest, StoreUserData_BeforeInitialize)
{
    const int kRegistrationId = 0;
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        StoreUserData(kRegistrationId, GURL("https://example.com"),
            { { "key", "data" } }));
}

TEST_F(ServiceWorkerStorageTest, GetUserData_BeforeInitialize)
{
    const int kRegistrationId = 0;
    std::vector<std::string> data_out;
    EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
        GetUserData(kRegistrationId, { "key" }, &data_out));
}

TEST_F(ServiceWorkerStorageTest, ClearUserData_BeforeInitialize)
{
    const int kRegistrationId = 0;
    EXPECT_EQ(SERVICE_WORKER_OK, ClearUserData(kRegistrationId, { "key" }));
}

TEST_F(ServiceWorkerStorageTest,
    GetUserDataForAllRegistrations_BeforeInitialize)
{
    std::vector<std::pair<int64_t, std::string>> data_list_out;
    EXPECT_EQ(SERVICE_WORKER_OK,
        GetUserDataForAllRegistrations("key", &data_list_out));
    EXPECT_TRUE(data_list_out.empty());
}

class ServiceWorkerResourceStorageTest : public ServiceWorkerStorageTest {
public:
    void SetUp() override
    {
        ServiceWorkerStorageTest::SetUp();
        LazyInitialize();

        scope_ = GURL("http://www.test.not/scope/");
        script_ = GURL("http://www.test.not/script.js");
        import_ = GURL("http://www.test.not/import.js");
        document_url_ = GURL("http://www.test.not/scope/document.html");
        registration_id_ = storage()->NewRegistrationId();
        version_id_ = storage()->NewVersionId();
        resource_id1_ = storage()->NewResourceId();
        resource_id2_ = storage()->NewResourceId();
        resource_id1_size_ = 239193;
        resource_id2_size_ = 59923;

        // Cons up a new registration+version with two script resources.
        RegistrationData data;
        data.registration_id = registration_id_;
        data.scope = scope_;
        data.script = script_;
        data.version_id = version_id_;
        data.is_active = false;
        std::vector<ResourceRecord> resources;
        resources.push_back(
            ResourceRecord(resource_id1_, script_, resource_id1_size_));
        resources.push_back(
            ResourceRecord(resource_id2_, import_, resource_id2_size_));
        registration_ = storage()->GetOrCreateRegistration(data, resources);
        registration_->waiting_version()->SetStatus(ServiceWorkerVersion::NEW);

        // Add the resources ids to the uncommitted list.
        storage()->StoreUncommittedResourceId(resource_id1_);
        storage()->StoreUncommittedResourceId(resource_id2_);
        base::RunLoop().RunUntilIdle();
        std::set<int64_t> verify_ids;
        EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
            storage()->database_->GetUncommittedResourceIds(&verify_ids));
        EXPECT_EQ(2u, verify_ids.size());

        // And dump something in the disk cache for them.
        WriteBasicResponse(storage(), resource_id1_);
        WriteBasicResponse(storage(), resource_id2_);
        EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
        EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));

        // Storing the registration/version should take the resources ids out
        // of the uncommitted list.
        EXPECT_EQ(
            SERVICE_WORKER_OK,
            StoreRegistration(registration_, registration_->waiting_version()));
        verify_ids.clear();
        EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
            storage()->database_->GetUncommittedResourceIds(&verify_ids));
        EXPECT_TRUE(verify_ids.empty());
    }

protected:
    GURL scope_;
    GURL script_;
    GURL import_;
    GURL document_url_;
    int64_t registration_id_;
    int64_t version_id_;
    int64_t resource_id1_;
    uint64_t resource_id1_size_;
    int64_t resource_id2_;
    uint64_t resource_id2_size_;
    scoped_refptr<ServiceWorkerRegistration> registration_;
};

class ServiceWorkerResourceStorageDiskTest
    : public ServiceWorkerResourceStorageTest {
public:
    void SetUp() override
    {
        ASSERT_TRUE(InitUserDataDirectory());
        ServiceWorkerResourceStorageTest::SetUp();
    }
};

TEST_F(ServiceWorkerResourceStorageTest,
    WriteMetadataWithServiceWorkerResponseMetadataWriter)
{
    const char kMetadata1[] = "Test metadata";
    const char kMetadata2[] = "small";
    int64_t new_resource_id_ = storage()->NewResourceId();
    // Writing metadata to nonexistent resoirce ID must fail.
    EXPECT_GE(0, WriteResponseMetadata(storage(), new_resource_id_, kMetadata1));

    // Check metadata is written.
    EXPECT_EQ(static_cast<int>(strlen(kMetadata1)),
        WriteResponseMetadata(storage(), resource_id1_, kMetadata1));
    EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata1));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));

    // Check metadata is written and truncated.
    EXPECT_EQ(static_cast<int>(strlen(kMetadata2)),
        WriteResponseMetadata(storage(), resource_id1_, kMetadata2));
    EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata2));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));

    // Check metadata is deleted.
    EXPECT_EQ(0, WriteResponseMetadata(storage(), resource_id1_, ""));
    EXPECT_FALSE(VerifyResponseMetadata(storage(), resource_id1_, ""));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
}

TEST_F(ServiceWorkerResourceStorageTest,
    WriteMetadataWithServiceWorkerScriptCacheMap)
{
    const char kMetadata1[] = "Test metadata";
    const char kMetadata2[] = "small";
    ServiceWorkerVersion* version = registration_->waiting_version();
    EXPECT_TRUE(version);

    // Writing metadata to nonexistent URL must fail.
    EXPECT_GE(0,
        WriteMetadata(version, GURL("http://www.test.not/nonexistent.js"),
            kMetadata1));
    // Clearing metadata of nonexistent URL must fail.
    EXPECT_GE(0,
        ClearMetadata(version, GURL("http://www.test.not/nonexistent.js")));

    // Check metadata is written.
    EXPECT_EQ(static_cast<int>(strlen(kMetadata1)),
        WriteMetadata(version, script_, kMetadata1));
    EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata1));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));

    // Check metadata is written and truncated.
    EXPECT_EQ(static_cast<int>(strlen(kMetadata2)),
        WriteMetadata(version, script_, kMetadata2));
    EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata2));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));

    // Check metadata is deleted.
    EXPECT_EQ(0, ClearMetadata(version, script_));
    EXPECT_FALSE(VerifyResponseMetadata(storage(), resource_id1_, ""));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
}

TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_NoLiveVersion)
{
    bool was_called = false;
    ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
    std::set<int64_t> verify_ids;

    registration_->SetWaitingVersion(NULL);
    registration_ = NULL;

    // Deleting the registration should result in the resources being added to the
    // purgeable list and then doomed in the disk cache and removed from that
    // list.
    storage()->DeleteRegistration(
        registration_id_,
        scope_.GetOrigin(),
        base::Bind(&VerifyPurgeableListStatusCallback,
            base::Unretained(storage()->database_.get()),
            &verify_ids,
            &was_called,
            &result));
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(was_called);
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_TRUE(verify_ids.empty());

    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}

TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_WaitingVersion)
{
    bool was_called = false;
    ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
    std::set<int64_t> verify_ids;

    // Deleting the registration should result in the resources being added to the
    // purgeable list and then doomed in the disk cache and removed from that
    // list.
    storage()->DeleteRegistration(
        registration_->id(),
        scope_.GetOrigin(),
        base::Bind(&VerifyPurgeableListStatusCallback,
            base::Unretained(storage()->database_.get()),
            &verify_ids,
            &was_called,
            &result));
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(was_called);
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_EQ(2u, verify_ids.size());

    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, false));

    // Doom the version, now it happens.
    registration_->waiting_version()->Doom();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_TRUE(verify_ids.empty());

    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}

TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_ActiveVersion)
{
    // Promote the worker to active and add a controllee.
    registration_->SetActiveVersion(registration_->waiting_version());
    storage()->UpdateToActiveState(
        registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
    std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
        33 /* dummy render process id */, MSG_ROUTING_NONE,
        1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
        ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
        context()->AsWeakPtr(), NULL));
    registration_->active_version()->AddControllee(host.get());

    bool was_called = false;
    ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
    std::set<int64_t> verify_ids;

    // Deleting the registration should move the resources to the purgeable list
    // but keep them available.
    storage()->DeleteRegistration(
        registration_->id(),
        scope_.GetOrigin(),
        base::Bind(&VerifyPurgeableListStatusCallback,
            base::Unretained(storage()->database_.get()),
            &verify_ids,
            &was_called,
            &result));
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(was_called);
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_EQ(2u, verify_ids.size());

    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));

    // Removing the controllee should cause the resources to be deleted.
    registration_->active_version()->RemoveControllee(host.get());
    registration_->active_version()->Doom();
    base::RunLoop().RunUntilIdle();
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_TRUE(verify_ids.empty());

    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}

TEST_F(ServiceWorkerResourceStorageDiskTest, CleanupOnRestart)
{
    // Promote the worker to active and add a controllee.
    registration_->SetActiveVersion(registration_->waiting_version());
    registration_->SetWaitingVersion(NULL);
    storage()->UpdateToActiveState(
        registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
    std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
        33 /* dummy render process id */, MSG_ROUTING_NONE,
        1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
        ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
        context()->AsWeakPtr(), NULL));
    registration_->active_version()->AddControllee(host.get());

    bool was_called = false;
    ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
    std::set<int64_t> verify_ids;

    // Deleting the registration should move the resources to the purgeable list
    // but keep them available.
    storage()->DeleteRegistration(
        registration_->id(),
        scope_.GetOrigin(),
        base::Bind(&VerifyPurgeableListStatusCallback,
            base::Unretained(storage()->database_.get()),
            &verify_ids,
            &was_called,
            &result));
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(was_called);
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_EQ(2u, verify_ids.size());

    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));

    // Also add an uncommitted resource.
    int64_t kStaleUncommittedResourceId = storage()->NewResourceId();
    storage()->StoreUncommittedResourceId(kStaleUncommittedResourceId);
    base::RunLoop().RunUntilIdle();
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetUncommittedResourceIds(&verify_ids));
    EXPECT_EQ(1u, verify_ids.size());
    WriteBasicResponse(storage(), kStaleUncommittedResourceId);
    EXPECT_TRUE(
        VerifyBasicResponse(storage(), kStaleUncommittedResourceId, true));

    // Simulate browser shutdown. The purgeable and uncommitted resources are now
    // stale.
    InitializeTestHelper();
    LazyInitialize();

    // Store a new uncommitted resource. This triggers stale resource cleanup.
    int64_t kNewResourceId = storage()->NewResourceId();
    WriteBasicResponse(storage(), kNewResourceId);
    storage()->StoreUncommittedResourceId(kNewResourceId);
    base::RunLoop().RunUntilIdle();

    // The stale resources should be purged, but the new resource should persist.
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetUncommittedResourceIds(&verify_ids));
    ASSERT_EQ(1u, verify_ids.size());
    EXPECT_EQ(kNewResourceId, *verify_ids.begin());

    // Purging resources needs interactions with SimpleCache's worker thread,
    // so single RunUntilIdle() call may not be sufficient.
    while (storage()->is_purge_pending_)
        base::RunLoop().RunUntilIdle();

    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_TRUE(verify_ids.empty());
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
    EXPECT_FALSE(
        VerifyBasicResponse(storage(), kStaleUncommittedResourceId, false));
    EXPECT_TRUE(VerifyBasicResponse(storage(), kNewResourceId, true));
}

TEST_F(ServiceWorkerResourceStorageDiskTest, DeleteAndStartOver)
{
    EXPECT_FALSE(storage()->IsDisabled());
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));

    base::RunLoop run_loop;
    ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
    storage()->DeleteAndStartOver(
        base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
    run_loop.Run();

    EXPECT_EQ(SERVICE_WORKER_OK, status);
    EXPECT_TRUE(storage()->IsDisabled());
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
}

TEST_F(ServiceWorkerResourceStorageDiskTest,
    DeleteAndStartOver_UnrelatedFileExists)
{
    EXPECT_FALSE(storage()->IsDisabled());
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));

    // Create an unrelated file in the database directory to make sure such a file
    // does not prevent DeleteAndStartOver.
    base::FilePath file_path;
    ASSERT_TRUE(
        base::CreateTemporaryFileInDir(storage()->GetDatabasePath(), &file_path));
    ASSERT_TRUE(base::PathExists(file_path));

    base::RunLoop run_loop;
    ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
    storage()->DeleteAndStartOver(
        base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
    run_loop.Run();

    EXPECT_EQ(SERVICE_WORKER_OK, status);
    EXPECT_TRUE(storage()->IsDisabled());
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
}

TEST_F(ServiceWorkerResourceStorageDiskTest,
    DeleteAndStartOver_OpenedFileExists)
{
    EXPECT_FALSE(storage()->IsDisabled());
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
    ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));

    // Create an unrelated opened file in the database directory to make sure such
    // a file does not prevent DeleteAndStartOver on non-Windows platforms.
    base::FilePath file_path;
    base::ScopedFILE file(base::CreateAndOpenTemporaryFileInDir(
        storage()->GetDatabasePath(), &file_path));
    ASSERT_TRUE(file);
    ASSERT_TRUE(base::PathExists(file_path));

    base::RunLoop run_loop;
    ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
    storage()->DeleteAndStartOver(
        base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
    run_loop.Run();

#if defined(OS_WIN)
    // On Windows, deleting the directory containing an opened file should fail.
    EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED, status);
    EXPECT_TRUE(storage()->IsDisabled());
    EXPECT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
    EXPECT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));
#else
    EXPECT_EQ(SERVICE_WORKER_OK, status);
    EXPECT_TRUE(storage()->IsDisabled());
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
    EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
#endif
}

TEST_F(ServiceWorkerResourceStorageTest, UpdateRegistration)
{
    // Promote the worker to active worker and add a controllee.
    registration_->SetActiveVersion(registration_->waiting_version());
    storage()->UpdateToActiveState(
        registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
    std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
        33 /* dummy render process id */, MSG_ROUTING_NONE,
        1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
        ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
        context()->AsWeakPtr(), NULL));
    registration_->active_version()->AddControllee(host.get());

    bool was_called = false;
    ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
    std::set<int64_t> verify_ids;

    // Make an updated registration.
    scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
        registration_.get(), script_, storage()->NewVersionId(),
        context()->AsWeakPtr());
    live_version->SetStatus(ServiceWorkerVersion::NEW);
    registration_->SetWaitingVersion(live_version);
    std::vector<ServiceWorkerDatabase::ResourceRecord> records;
    records.push_back(ServiceWorkerDatabase::ResourceRecord(
        10, live_version->script_url(), 100));
    live_version->script_cache_map()->SetResources(records);
    live_version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);

    // Writing the registration should move the old version's resources to the
    // purgeable list but keep them available.
    storage()->StoreRegistration(
        registration_.get(),
        registration_->waiting_version(),
        base::Bind(&VerifyPurgeableListStatusCallback,
            base::Unretained(storage()->database_.get()),
            &verify_ids,
            &was_called,
            &result));
    base::RunLoop().RunUntilIdle();
    ASSERT_TRUE(was_called);
    EXPECT_EQ(SERVICE_WORKER_OK, result);
    EXPECT_EQ(2u, verify_ids.size());
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_EQ(2u, verify_ids.size());

    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, false));

    // Removing the controllee should cause the old version's resources to be
    // deleted.
    registration_->active_version()->RemoveControllee(host.get());
    registration_->active_version()->Doom();
    base::RunLoop().RunUntilIdle();
    verify_ids.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        storage()->database_->GetPurgeableResourceIds(&verify_ids));
    EXPECT_TRUE(verify_ids.empty());

    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
    EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}

TEST_F(ServiceWorkerStorageTest, FindRegistration_LongestScopeMatch)
{
    const GURL kDocumentUrl("http://www.example.com/scope/foo");
    scoped_refptr<ServiceWorkerRegistration> found_registration;

    // Registration for "/scope/".
    const GURL kScope1("http://www.example.com/scope/");
    const GURL kScript1("http://www.example.com/script1.js");
    const int64_t kRegistrationId1 = 1;
    const int64_t kVersionId1 = 1;
    scoped_refptr<ServiceWorkerRegistration> live_registration1 = new ServiceWorkerRegistration(kScope1, kRegistrationId1,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version1 = new ServiceWorkerVersion(
        live_registration1.get(), kScript1, kVersionId1, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records1;
    records1.push_back(ServiceWorkerDatabase::ResourceRecord(
        1, live_version1->script_url(), 100));
    live_version1->script_cache_map()->SetResources(records1);
    live_version1->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version1->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_registration1->SetWaitingVersion(live_version1);

    // Registration for "/scope/foo".
    const GURL kScope2("http://www.example.com/scope/foo");
    const GURL kScript2("http://www.example.com/script2.js");
    const int64_t kRegistrationId2 = 2;
    const int64_t kVersionId2 = 2;
    scoped_refptr<ServiceWorkerRegistration> live_registration2 = new ServiceWorkerRegistration(kScope2, kRegistrationId2,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version2 = new ServiceWorkerVersion(
        live_registration2.get(), kScript2, kVersionId2, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records2;
    records2.push_back(ServiceWorkerDatabase::ResourceRecord(
        2, live_version2->script_url(), 100));
    live_version2->script_cache_map()->SetResources(records2);
    live_version2->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version2->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_registration2->SetWaitingVersion(live_version2);

    // Registration for "/scope/foobar".
    const GURL kScope3("http://www.example.com/scope/foobar");
    const GURL kScript3("http://www.example.com/script3.js");
    const int64_t kRegistrationId3 = 3;
    const int64_t kVersionId3 = 3;
    scoped_refptr<ServiceWorkerRegistration> live_registration3 = new ServiceWorkerRegistration(kScope3, kRegistrationId3,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version3 = new ServiceWorkerVersion(
        live_registration3.get(), kScript3, kVersionId3, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records3;
    records3.push_back(ServiceWorkerDatabase::ResourceRecord(
        3, live_version3->script_url(), 100));
    live_version3->script_cache_map()->SetResources(records3);
    live_version3->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version3->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_registration3->SetWaitingVersion(live_version3);

    // Notify storage of they being installed.
    storage()->NotifyInstallingRegistration(live_registration1.get());
    storage()->NotifyInstallingRegistration(live_registration2.get());
    storage()->NotifyInstallingRegistration(live_registration3.get());

    // Find a registration among installing ones.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_EQ(live_registration2, found_registration);
    found_registration = NULL;

    // Store registrations.
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration1, live_version1));
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration2, live_version2));
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration3, live_version3));

    // Notify storage of installations no longer happening.
    storage()->NotifyDoneInstallingRegistration(
        live_registration1.get(), NULL, SERVICE_WORKER_OK);
    storage()->NotifyDoneInstallingRegistration(
        live_registration2.get(), NULL, SERVICE_WORKER_OK);
    storage()->NotifyDoneInstallingRegistration(
        live_registration3.get(), NULL, SERVICE_WORKER_OK);

    // Find a registration among installed ones.
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kDocumentUrl, &found_registration));
    EXPECT_EQ(live_registration2, found_registration);
}

// Test fixture that uses disk storage, rather than memory. Useful for tests
// that test persistence by simulating browser shutdown and restart.
class ServiceWorkerStorageDiskTest : public ServiceWorkerStorageTest {
public:
    void SetUp() override
    {
        ASSERT_TRUE(InitUserDataDirectory());
        ServiceWorkerStorageTest::SetUp();
    }
};

TEST_F(ServiceWorkerStorageDiskTest, OriginHasForeignFetchRegistrations)
{
    LazyInitialize();

    // Registration 1 for http://www.example.com
    const GURL kScope1("http://www.example.com/scope/");
    const GURL kScript1("http://www.example.com/script1.js");
    const int64_t kRegistrationId1 = 1;
    const int64_t kVersionId1 = 1;
    scoped_refptr<ServiceWorkerRegistration> live_registration1 = new ServiceWorkerRegistration(kScope1, kRegistrationId1,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version1 = new ServiceWorkerVersion(
        live_registration1.get(), kScript1, kVersionId1, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records1;
    records1.push_back(ServiceWorkerDatabase::ResourceRecord(
        1, live_version1->script_url(), 100));
    live_version1->script_cache_map()->SetResources(records1);
    live_version1->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version1->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_version1->set_foreign_fetch_scopes(std::vector<GURL>(1, kScope1));
    live_registration1->SetWaitingVersion(live_version1);

    // Registration 2 for http://www.example.com
    const GURL kScope2("http://www.example.com/scope/foo");
    const GURL kScript2("http://www.example.com/script2.js");
    const int64_t kRegistrationId2 = 2;
    const int64_t kVersionId2 = 2;
    scoped_refptr<ServiceWorkerRegistration> live_registration2 = new ServiceWorkerRegistration(kScope2, kRegistrationId2,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version2 = new ServiceWorkerVersion(
        live_registration2.get(), kScript2, kVersionId2, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records2;
    records2.push_back(ServiceWorkerDatabase::ResourceRecord(
        2, live_version2->script_url(), 100));
    live_version2->script_cache_map()->SetResources(records2);
    live_version2->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version2->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_version2->set_foreign_fetch_scopes(std::vector<GURL>(1, kScope2));
    live_registration2->SetWaitingVersion(live_version2);

    // Registration for http://www.test.com
    const GURL kScope3("http://www.test.com/scope/foobar");
    const GURL kScript3("http://www.test.com/script3.js");
    const int64_t kRegistrationId3 = 3;
    const int64_t kVersionId3 = 3;
    scoped_refptr<ServiceWorkerRegistration> live_registration3 = new ServiceWorkerRegistration(kScope3, kRegistrationId3,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> live_version3 = new ServiceWorkerVersion(
        live_registration3.get(), kScript3, kVersionId3, context()->AsWeakPtr());
    std::vector<ServiceWorkerDatabase::ResourceRecord> records3;
    records3.push_back(ServiceWorkerDatabase::ResourceRecord(
        3, live_version3->script_url(), 100));
    live_version3->script_cache_map()->SetResources(records3);
    live_version3->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    live_version3->SetStatus(ServiceWorkerVersion::INSTALLED);
    live_registration3->SetWaitingVersion(live_version3);

    // Neither origin should have registrations before they are stored.
    const GURL kOrigin1 = kScope1.GetOrigin();
    const GURL kOrigin2 = kScope3.GetOrigin();
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));

    // Store all registrations.
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration1, live_version1));
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration2, live_version2));
    EXPECT_EQ(SERVICE_WORKER_OK,
        StoreRegistration(live_registration3, live_version3));

    // Now first origin should have foreign fetch registrations, second doesn't.
    EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));

    // Remove one registration at first origin.
    EXPECT_EQ(SERVICE_WORKER_OK,
        DeleteRegistration(kRegistrationId1, kScope1.GetOrigin()));

    // First origin should still have a registration left.
    EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));

    // Simulate browser shutdown and restart.
    live_registration1 = nullptr;
    live_version1 = nullptr;
    live_registration2 = nullptr;
    live_version2 = nullptr;
    live_registration3 = nullptr;
    live_version3 = nullptr;
    InitializeTestHelper();
    LazyInitialize();

    // First origin should still have a registration left.
    EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));

    // Remove other registration at first origin.
    EXPECT_EQ(SERVICE_WORKER_OK,
        DeleteRegistration(kRegistrationId2, kScope2.GetOrigin()));

    // No foreign fetch registrations remain.
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
    EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
}

TEST_F(ServiceWorkerStorageTest, OriginTrialsAbsentEntryAndEmptyEntry)
{
    const GURL origin1("http://www1.example.com");
    const GURL scope1("http://www1.example.com/foo/");
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = scope1;
    data1.script = GURL(origin1.spec() + "/script.js");
    data1.version_id = 1000;
    data1.is_active = true;
    data1.resources_total_size_bytes = 100;
    // Don't set origin_trial_tokens to simulate old database entry.
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources1;
    resources1.push_back(
        ServiceWorkerDatabase::ResourceRecord(1, data1.script, 100));
    WriteRegistration(data1, resources1);

    const GURL origin2("http://www2.example.com");
    const GURL scope2("http://www2.example.com/foo/");
    RegistrationData data2;
    data2.registration_id = 200;
    data2.scope = scope2;
    data2.script = GURL(origin2.spec() + "/script.js");
    data2.version_id = 2000;
    data2.is_active = true;
    data2.resources_total_size_bytes = 200;
    // Set empty origin_trial_tokens.
    data2.origin_trial_tokens = TrialTokenValidator::FeatureToTokensMap();
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources2;
    resources2.push_back(
        ServiceWorkerDatabase::ResourceRecord(2, data2.script, 200));
    WriteRegistration(data2, resources2);

    scoped_refptr<ServiceWorkerRegistration> found_registration;

    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(scope1, &found_registration));
    ASSERT_TRUE(found_registration->active_version());
    // origin_trial_tokens must be unset.
    EXPECT_FALSE(found_registration->active_version()->origin_trial_tokens());

    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(scope2, &found_registration));
    ASSERT_TRUE(found_registration->active_version());
    // Empty origin_trial_tokens must exist.
    ASSERT_TRUE(found_registration->active_version()->origin_trial_tokens());
    EXPECT_TRUE(
        found_registration->active_version()->origin_trial_tokens()->empty());
}

class ServiceWorkerStorageOriginTrialsDiskTest
    : public ServiceWorkerStorageTest {
public:
    ServiceWorkerStorageOriginTrialsDiskTest()
    {
        SetContentClient(&test_content_client_);
    }
    ~ServiceWorkerStorageOriginTrialsDiskTest() override
    {
        SetContentClient(nullptr);
    }
    void SetUp() override
    {
        ASSERT_TRUE(InitUserDataDirectory());
        ServiceWorkerStorageTest::SetUp();
    }

private:
    class TestOriginTrialPolicy : public OriginTrialPolicy {
    public:
        base::StringPiece GetPublicKey() const override
        {
            return base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey),
                arraysize(kTestPublicKey));
        }
        bool IsFeatureDisabled(base::StringPiece feature) const override
        {
            return false;
        }
    };
    class TestContentClient : public ContentClient {
    public:
        // ContentRendererClient methods
        OriginTrialPolicy* GetOriginTrialPolicy() override
        {
            return &origin_trial_policy_;
        }

    private:
        TestOriginTrialPolicy origin_trial_policy_;
    };
    TestContentClient test_content_client_;
};

TEST_F(ServiceWorkerStorageOriginTrialsDiskTest, FromMainScript)
{
    LazyInitialize();
    const GURL kScope("https://valid.example.com/scope");
    const GURL kScript("https://valid.example.com/script.js");
    const int64_t kRegistrationId = 1;
    const int64_t kVersionId = 1;
    scoped_refptr<ServiceWorkerRegistration> registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
        registration.get(), kScript, kVersionId, context()->AsWeakPtr());

    net::HttpResponseInfo http_info;
    http_info.ssl_info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
    EXPECT_TRUE(http_info.ssl_info.is_valid());
    http_info.ssl_info.security_bits = 0x100;
    // SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
    http_info.ssl_info.connection_status = 0x300039;

    const std::string kHTTPHeaderLine("HTTP/1.1 200 OK\n\n");
    const std::string kOriginTrial("Origin-Trial: ");
    // Token for Feature1 which expires 2033-05-18.
    // generate_token.py valid.example.com Feature1 --expire-timestamp=2000000000
    // TODO(horo): Generate this sample token during the build.
    const std::string kFeature1Token(
        "AtiUXksymWhTv5ipBE7853JytiYb0RMj3wtEBjqu3PeufQPwV1oEaNjHt4R/oEBfcK0UiWlA"
        "P2b9BE2/eThqcAYAAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
        "NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMSIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==");
    // Token for Feature2 which expires 2033-05-18.
    // generate_token.py valid.example.com Feature2 --expire-timestamp=2000000000
    // TODO(horo): Generate this sample token during the build.
    const std::string kFeature2Token1(
        "ApmHVC6Dpez0KQNBy13o6cGuoB5AgzOLN0keQMyAN5mjebCwR0MA8/IyjKQIlyom2RuJVg/u"
        "LmnqEpldfewkbA8AAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
        "NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMiIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==");
    // Token for Feature2 which expires 2036-07-18.
    // generate_token.py valid.example.com Feature2 --expire-timestamp=2100000000
    // TODO(horo): Generate this sample token during the build.
    const std::string kFeature2Token2(
        "AmV2SSxrYstE2zSwZToy7brAbIJakd146apC/6+VDflLmc5yDfJlHGILe5+ZynlcliG7clOR"
        "fHhXCzS5Lh1v4AAAAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
        "NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMiIsICJleHBpcnkiOiAyMTAwMDAwMDAwfQ==");
    // Token for Feature3 which expired 2001-09-09.
    // generate_token.py valid.example.com Feature3 --expire-timestamp=1000000000
    const std::string kFeature3ExpiredToken(
        "AtSAc03z4qvid34W4MHMxyRFUJKlubZ+P5cs5yg6EiBWcagVbnm5uBgJMJN34pag7D5RywGV"
        "ol2RFf+4Sdm1hQ4AAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
        "NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMyIsICJleHBpcnkiOiAxMDAwMDAwMDAwfQ==");
    http_info.headers = make_scoped_refptr(new net::HttpResponseHeaders(""));
    http_info.headers->AddHeader(kOriginTrial + kFeature1Token);
    http_info.headers->AddHeader(kOriginTrial + kFeature2Token1);
    http_info.headers->AddHeader(kOriginTrial + kFeature2Token2);
    http_info.headers->AddHeader(kOriginTrial + kFeature3ExpiredToken);
    version->SetMainScriptHttpResponseInfo(http_info);
    ASSERT_TRUE(version->origin_trial_tokens());
    const TrialTokenValidator::FeatureToTokensMap& tokens = *version->origin_trial_tokens();
    ASSERT_EQ(2UL, tokens.size());
    ASSERT_EQ(1UL, tokens.at("Feature1").size());
    EXPECT_EQ(kFeature1Token, tokens.at("Feature1")[0]);
    ASSERT_EQ(2UL, tokens.at("Feature2").size());
    EXPECT_EQ(kFeature2Token1, tokens.at("Feature2")[0]);
    EXPECT_EQ(kFeature2Token2, tokens.at("Feature2")[1]);

    std::vector<ServiceWorkerDatabase::ResourceRecord> record;
    record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
    version->script_cache_map()->SetResources(record);
    version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    version->SetStatus(ServiceWorkerVersion::INSTALLED);
    registration->SetActiveVersion(version);

    EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));

    // Simulate browser shutdown and restart.
    registration = nullptr;
    version = nullptr;
    InitializeTestHelper();
    LazyInitialize();

    scoped_refptr<ServiceWorkerRegistration> found_registration;
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kScope, &found_registration));
    ASSERT_TRUE(found_registration->active_version());
    const TrialTokenValidator::FeatureToTokensMap& found_tokens = *found_registration->active_version()->origin_trial_tokens();
    ASSERT_EQ(2UL, found_tokens.size());
    ASSERT_EQ(1UL, found_tokens.at("Feature1").size());
    EXPECT_EQ(kFeature1Token, found_tokens.at("Feature1")[0]);
    ASSERT_EQ(2UL, found_tokens.at("Feature2").size());
    EXPECT_EQ(kFeature2Token1, found_tokens.at("Feature2")[0]);
    EXPECT_EQ(kFeature2Token2, found_tokens.at("Feature2")[1]);
}

// Tests loading a registration that has no navigation preload state.
TEST_F(ServiceWorkerStorageTest, AbsentNavigationPreloadState)
{
    const GURL origin1("http://www1.example.com");
    const GURL scope1("http://www1.example.com/foo/");
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = scope1;
    data1.script = GURL(origin1.spec() + "/script.js");
    data1.version_id = 1000;
    data1.is_active = true;
    data1.resources_total_size_bytes = 100;
    // Don't set navigation preload state to simulate old database entry.
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources1;
    resources1.push_back(
        ServiceWorkerDatabase::ResourceRecord(1, data1.script, 100));
    WriteRegistration(data1, resources1);

    scoped_refptr<ServiceWorkerRegistration> found_registration;
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(scope1, &found_registration));
    const NavigationPreloadState& registration_state = found_registration->navigation_preload_state();
    EXPECT_FALSE(registration_state.enabled);
    EXPECT_EQ("true", registration_state.header);
    ASSERT_TRUE(found_registration->active_version());
    const NavigationPreloadState& state = found_registration->active_version()->navigation_preload_state();
    EXPECT_FALSE(state.enabled);
    EXPECT_EQ("true", state.header);
}

// Tests loading a registration with a disabled navigation preload
// state.
TEST_F(ServiceWorkerStorageDiskTest, DisabledNavigationPreloadState)
{
    LazyInitialize();
    const GURL kScope("https://valid.example.com/scope");
    const GURL kScript("https://valid.example.com/script.js");
    const int64_t kRegistrationId = 1;
    const int64_t kVersionId = 1;
    scoped_refptr<ServiceWorkerRegistration> registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
        registration.get(), kScript, kVersionId, context()->AsWeakPtr());

    std::vector<ServiceWorkerDatabase::ResourceRecord> record;
    record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
    version->script_cache_map()->SetResources(record);
    version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    version->SetStatus(ServiceWorkerVersion::ACTIVATED);
    registration->SetActiveVersion(version);
    registration->EnableNavigationPreload(false);

    EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));

    // Simulate browser shutdown and restart.
    registration = nullptr;
    version = nullptr;
    InitializeTestHelper();
    LazyInitialize();

    scoped_refptr<ServiceWorkerRegistration> found_registration;
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kScope, &found_registration));
    const NavigationPreloadState& registration_state = found_registration->navigation_preload_state();
    EXPECT_FALSE(registration_state.enabled);
    EXPECT_EQ("true", registration_state.header);
    ASSERT_TRUE(found_registration->active_version());
    const NavigationPreloadState& state = found_registration->active_version()->navigation_preload_state();
    EXPECT_FALSE(state.enabled);
    EXPECT_EQ("true", state.header);
}

// Tests loading a registration with an enabled navigation preload state, as
// well as a custom header value.
TEST_F(ServiceWorkerStorageDiskTest, EnabledNavigationPreloadState)
{
    LazyInitialize();
    const GURL kScope("https://valid.example.com/scope");
    const GURL kScript("https://valid.example.com/script.js");
    const std::string kHeaderValue("custom header value");
    const int64_t kRegistrationId = 1;
    const int64_t kVersionId = 1;
    scoped_refptr<ServiceWorkerRegistration> registration = new ServiceWorkerRegistration(kScope, kRegistrationId,
        context()->AsWeakPtr());
    scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
        registration.get(), kScript, kVersionId, context()->AsWeakPtr());

    std::vector<ServiceWorkerDatabase::ResourceRecord> record;
    record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
    version->script_cache_map()->SetResources(record);
    version->set_fetch_handler_existence(
        ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
    version->SetStatus(ServiceWorkerVersion::ACTIVATED);
    registration->SetActiveVersion(version);
    registration->EnableNavigationPreload(true);
    registration->SetNavigationPreloadHeader(kHeaderValue);

    EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));

    // Simulate browser shutdown and restart.
    registration = nullptr;
    version = nullptr;
    InitializeTestHelper();
    LazyInitialize();

    scoped_refptr<ServiceWorkerRegistration> found_registration;
    EXPECT_EQ(SERVICE_WORKER_OK,
        FindRegistrationForDocument(kScope, &found_registration));
    const NavigationPreloadState& registration_state = found_registration->navigation_preload_state();
    EXPECT_TRUE(registration_state.enabled);
    EXPECT_EQ(kHeaderValue, registration_state.header);
    ASSERT_TRUE(found_registration->active_version());
    const NavigationPreloadState& state = found_registration->active_version()->navigation_preload_state();
    EXPECT_TRUE(state.enabled);
    EXPECT_EQ(kHeaderValue, state.header);
}

} // namespace content
